<?php

namespace App\Command\ResumeParsing\Service\ParsingSpecial;

class ParsingSpecialXml extends ParsingSpecial {
    protected $br = "【br】";
    protected $debugFormat = -1;
    protected $titleXpath = "//p";
    protected $isProperDocx = false;
    protected $space = "【nbsp(\s+)】";

    /**
     * @var \DOMDocument
     */
    protected $dom;
    /**
     * @var \DOMXPath
     */
    protected $xPath;

    /* 此范例适用于派生类构造函数中
            $this->get['template'] = function() {
                $this->listXPath("//templateTag[contains(., 'templateTagName')]");
                $result = $this->xPath->query();
                if($result->length < 1)	return '';
                return $this->formatTxt($result[0]->textContent);
            };
    */

    //自定义初始化数据逻辑，可以被重载
    protected function onParseInit(...$params) {
        if (count($params) < 1) throw new \ArgumentCountError('\DOMDocument $dom expected, none given');
        if (!$params[0] instanceof \DOMDocument) throw new \InvalidArgumentException('\DOMDocument $dom expected');
        $this->dom = $params[0];
        $this->xPath = new \DOMXPath($this->dom);
    }

    //自定义parse后行为，可以被重载
    protected function onParsed() {
        $this->markProperDocx();
    }

    //检测简历是否适用当前规则集，可以被重载自定义检测方式
    protected function test() {
        if (empty($this->get)) return false;
        if (strlen($this->titleXpath) > 10) {
            $titleCount = count($this->xPath->query($this->titleXpath));
            if ($titleCount > 2) return true;
            if ($titleCount < 1) return false;
        }
        if (empty($this->get['name']) || !is_callable($this->get['name']) || empty($this->get['name']())) return false;
        if (empty($this->get['mobile']) || !is_callable($this->get['mobile']) || empty($this->get['mobile']())) return false;
        return true;
    }

    protected function markProperDocx() {
        $test = $this->xPath->query("//document/body//p[ppr/rpr][1]");
        if (!$test->length) {
            $this->isProperDocx = false;
        }
        $this->br = $test->length ? '【br_' . time() . '】' : "\r\n";
        $brMark = $this->dom->createDocumentFragment();
        $brMark->appendXML($this->br);
        $brNodes = $this->xPath->query('//br');
        foreach ($brNodes as $brNode) {
            $brNode->parentNode->replaceChild(clone $brMark, $brNode);
        }
        if ($test->length) {
            $spacePrefix = '【nbsp_' . time();
            $this->space = $spacePrefix . '([^\S\r\n]+)】';
            $texts = $this->xPath->query("//t[@*[local-name()='xml:space' and .='preserve']]");
            foreach ($texts as $text) {
                if (empty($text->textContent))
                    $text->nodeValue = $spacePrefix . ' 】';
                else
                    $text->nodeValue = preg_replace('/\s+/u', $spacePrefix . '$0】', $text->textContent);
            }
            $this->isProperDocx = true;
        }
    }

    protected function groupFormatCallback($item) {
        if ($item instanceof \DOMNode) $item = $item->textContent;
        $item = $this->formatTxt($item, $this->isProperDocx ? false : 'auto');
        return parent::groupFormatCallback($item);
    }

    protected function recoverText($text) {
        return $this->formatTxt($text);
    }

    protected function formatTxt($text, ...$params) {
        if ($this->isProperDocx) $text = $this->internalRecover($this->func->formatTxt($text, true));
        return $this->func->formatTxt($text, ...$params);
    }

    protected function preg_get($pattern, $subject, $options = []) {
        if ($this->isProperDocx) {
            $subject = $this->internalRecover($this->func->formatTxt($subject, true));
        }
        return $this->func->preg_get($pattern, $subject, $options);
    }

    private function internalRecover($text) {
        $text = preg_replace('/' . $this->br . '/u', "\r\n", $text);
        return preg_replace('/' . $this->space . '/u', "$1", $text);
    }

    function getPart($title, $returnNodes = FALSE, $itemXpath = '*', $titleXpath = null) {
        if (empty($titleXpath)) $titleXpath = $this->titleXpath;
        if ($title instanceof \DOMNode) {
            $titleNode = $title;
        } elseif (is_string($title)) {
            $path = preg_replace('/^(\/{0,2}(?:(?:[A-Za-z]+:)?[A-Za-z]+\/)*(?:[A-Za-z]+:)?[A-Za-z]+\[)(.*)\]$/u', "$1($2) and starts-with(normalize-space(),'" . $title . "')]", $titleXpath, 1, $count); // . "/following-sibling::";
            if (!$count) $path = $titleXpath . "[starts-with(normalize-space(),'" . $title . "')]";
            //var_dump($path);exit;
            $resultTitle = $this->xPath->query($path);
            if ($resultTitle->length < 1) return $returnNodes ? [] : '';
            $titleNode = $resultTitle[0];
        } else {
            return $returnNodes ? [] : '';
        }
        $pattern = preg_replace('/^\/\/?(?:(?:[A-Za-z]+:)?[A-Za-z]+\/)*(?=(?:[A-Za-z]+:)?[A-Za-z]+)/u', '', $titleXpath);
        $result = $this->xPath->query("following-sibling::*[self::" . $itemXpath . " or self::" . $pattern . "]", $titleNode);
        $nextTitle = $this->xPath->query("following-sibling::" . $pattern . "[1]", $titleNode);
        $resultText = '';
        $resultArr = array();
        foreach ($result as $node) {
            if ($nextTitle->length && $nextTitle[0]->isSameNode($node)) break;
            if (!isset($node->textContent) || empty($text = $this->formatTxt($node->textContent))) continue;
            if ($returnNodes) {
                $resultArr[] = $node;
            } else {
                if ($this->isProperDocx) {
                    $this->printNode($node, $resultText);
                } else {
                    $resultText .= "\r\n" . $text;
                }
            }
        }
        return $returnNodes ? $resultArr : trim($resultText);
    }

    protected function printNode($node, &$result = null, $printTypes = ['p']) {
        $dict = [];
        foreach ($printTypes as $type) {
            $dict[$type] = true;
        }
        $returnMode = false;
        if (is_null($result)) {
            $result = '';
            $returnMode = true;
        }
        $this->appendNodeTo($node, $result, $dict);
        if ($returnMode) {
            $result = trim($result);
            return $result;
        }
    }

    private function appendNodeTo(&$node, &$result, &$printTypes) {
        if ($this->is_iterable($node)) {
            foreach ($node as $child) {
                if (!$child instanceof \DOMNode || (!isset($child->tagName) && empty(trim($child->textContent)))) continue;
                $this->appendNodeTo($child, $result, $printTypes);
            }
            return;
        }
        if (!$node instanceof \DOMNode) {
            return;
        }
        if (isset($printTypes[$node->localName])) {
            $result .= "\r\n" . $this->formatTxt($node->textContent);
        } else {
            $this->appendNodeTo($node->childNodes, $result, $printTypes);
        }
    }

    //调试用，打印指定XPath相关信息
    //autoFormat 1 - 普通；2 - 智能去除换行，3 - 所有换行替换为空格，4 - 去除全部换行，否则不处理
    protected function listXPath($queryPath, $autoFormat = -1, $exclude = array('html', 'body', 'document')) {
        echo "--------------------XPath Debugger【" . $queryPath . "】--------------------\r\n";
        $result = $this->xPath->query($queryPath);
        if (!$result || $result->length < 1) {
            var_dump($queryPath . "\treturns no results!");
            exit;
        }
        $resultCount = 0;
        foreach ($result as $node) {
            if ((!isset($node->tagName) && empty(trim($node->textContent))) || (isset($node->tagName) && in_array($node->tagName, $exclude))) continue;
            $resultCount++;
            $this->renderNode($node, $autoFormat);
        }
        if ($resultCount == 0) {
            if ($this->renderNode($result, $autoFormat) == 0) {
                var_dump($queryPath . "\treturns only empty results!");
            }
        }
        exit;
    }

    //调试用，打印指定DOMNode（或DOMNode集合）相关信息
    //autoFormat 1 - 普通；2 - 智能去除换行，3 - 所有换行替换为空格，4 - 去除全部换行，否则不处理
    protected function renderNode($node, $autoFormat = -1, $renderAttr = array('class', 'style', 'w:val', 'w:eastasia', 'w:cs')) {
        if ($this->is_iterable($node)) {
            $resultCount = 0;
            foreach ($node as $child) {
                if (!$child instanceof \DOMNode || (!isset($child->tagName) && empty(trim($child->textContent)))) continue;
                $resultCount++;
                $this->renderNode($child, $autoFormat);
            }
            return $resultCount;
        }
        if (!$node instanceof \DOMNode) {
            var_dump('Not a DOMNode: ', $node);
            return;
        }
        $xPath = $node->getNodePath();
        var_dump($xPath);
        if (($xPathSimple = preg_replace('/\[\d+\]/', '', $xPath)) != $xPath) var_dump($xPathSimple);
        if (isset($node->attributes)) {
            foreach ($renderAttr as $attrName) {
                $attr = $node->attributes->getNamedItem($attrName);
                if ($attr && !is_null($attrValue = $attr->value)) {
                    var_dump($attrName . ":\t" . $attrValue);
                }
            }
        }
        if ($autoFormat == -1) $autoFormat = $this->debugFormat;
        //if (is_numeric($autoFormat) && $autoFormat > 0 && mb_strpos($node->textContent, $this->br) !== FALSE) $autoFormat = 4;
        switch ($autoFormat) {
            case 1:
                $content = $this->func->formatTxt($node->textContent);
                break;
            case 2:
                $content = $this->func->formatTxt($node->textContent, 'auto');
                break;
            case 3:
                $content = $this->func->formatTxt($node->textContent, 'space');
                break;
            case 4:
                $content = $this->func->formatTxt($node->textContent, TRUE);
                break;
            case 0:
                $content = $node->textContent;
                break;
            default:
                $content = $this->formatTxt($node->textContent);
        }
        if (is_numeric($autoFormat) && $autoFormat > 0) $content = preg_replace('/' . preg_quote($this->br) . '/u', "\r\n", $content);
        if (!empty($content)) var_dump($content);
        echo "-------------------------------------------------------------------------------------------------------------------------------------\r\n";
    }
}